package com.tongtech.space.module.common.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.tongtech.space.common.pojo.Result;
import com.tongtech.space.common.pojo.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION;
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;

/**
 * 测试Service
 *
 * @author jinjiayu
 * @since 2021/2/1
 */
@Slf4j
@Service
public class TestService {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    RedissonClient redissonClient;

    // redisson分布式锁demo
    private RLock rLockDemo = null;

    /**
     * 当前类实例加载完毕之后获取RLock对象,当然也可以在具体的业务方法里来获取
     *
     * @author jinjiayu
     */
    @PostConstruct
    public void init() {
        // 初始化锁,锁名称自定义
        rLockDemo = redissonClient.getLock("lockName");
    }

    /**
     * 转换MultipartFile为file
     *
     * @param filePathPrefix
     * @param fileSuffixPath
     * @param multipartFiles
     * @return List<File>
     * @author jinjiayu
     */
    public List<File> transFerMultipartFileToFile(String filePathPrefix, String fileSuffixPath, MultipartFile... multipartFiles) {
        if (multipartFiles == null || multipartFiles.length < 1) {
            return new ArrayList<>();
        }

        ArrayList<File> files = new ArrayList<>();
        for (MultipartFile item : multipartFiles) {
            log.info(item.getOriginalFilename());
            File file = new File(filePathPrefix + UUID.randomUUID() + fileSuffixPath);
            try {
                item.transferTo(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
            files.add(file);
        }
        return files;
    }

    /**
     * 上传文件到第三方
     *
     * @param url
     * @param files
     * @author jinjiayu
     */
    public void uploadToThirdPart(String url, List<File> files) {
        if (CollectionUtils.isEmpty(files)) {
            return;
        }

        // LinkedMultiValueMap内部使用集合实现,所以对于同一个key可以对应多个文件
        MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
        for (File item : files) {
            FileSystemResource resource = new FileSystemResource(item);
            param.add("multipartFiles", resource);
        }
        log.info("开始上传文件: " + param);
        String result = restTemplate.postForObject(url, param, String.class);
        log.info("上传结果: " + result);
    }

    /**
     * 下载文件从后台
     * 1.设置文件ContentType类型
     * 2.设置文件头
     * 3.通过response获取ServletOutputStream对象(out)
     * 4.写到输出流(out)中
     * 5.关闭流
     *
     * Content-Type(内容类型,也是MIME协议的体现):
     *   multipart/form-data: 是http协议的mime类型的拓展,既可以上传文件等二进制数据(可以是多个)，也可以上传表单键值对，只是最后会转化为一条信息；
     *   application/octet-stream (binary): 从字面意思得知，只可以上传二进制数据，通常用来上传文件，由于没有键值，所以，一次只能上传一个文件。
     *   x-www-form-urlencoded: 只能上传键值对，并且键值对都是间隔分开的。
     *   ......
     *
     * Content-Disposition(MIME协议的扩展):
     *   用于指示客户端如何显示附加的文件。可以控制用户响应所得的内容存为一个文件时提供默认的文件名，文件直接在浏览器上显示或者在访问时弹出文件下载对话框。
     *   第一个参数:
     *     如果是inline:默认值，表示回复中的消息体会以页面的一部分或者整个页面的形式展示;
     *     或者是attachment:意味着消息体应该被下载到本地；大多数浏览器会呈现一个“保存为”的对话框，将filename的值预填为下载后的文件名，假如它存在的话
     *
     * 一般在设置响应的时候Content-Type和Content-Disposition都要设置
     * 我的发现: 谷歌浏览器中当response设置Content-Disposition为inline时,加上随意设置的一个Content-Type头部参数,那么文件还是会下载下来,
     *          而IE不会,不确定这是什么原因.
     *
     * @param filePath 文件路径
     * @param response
     * @author jinjiayu
     */
    public void downloadFromBack(String filePath, HttpServletResponse response) {
        // 将文件作为二进制流数据返回
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            File file = new File(filePath);
            if (!file.exists()) {
                return;
            }
            response.addHeader(CONTENT_LENGTH, "" + file.length());
            response.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE);
            response.addHeader(CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));

            // 将文件读取为二进制数据并写入响应流中 (也可以FileCopyUtils.copyToByteArray(file))
            byte[] bytes = Files.readAllBytes(Paths.get(filePath));
            outputStream.write(bytes);
            outputStream.flush();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * ZIP打包并生成新文件
     *
     * @param filePathPrefix 生成zip文件路径前缀
     * @param fileName 生成zip文件名
     * @param files 需要打包的文件
     * @return File
     * @author jinjiayu
     */
    public File zipPack(String filePathPrefix, String fileName, File... files) {
        File zipFile = new File(filePathPrefix);
        if (!zipFile.exists()) {
            zipFile.mkdirs();
        }
        zipFile = new File(zipFile, fileName + ".zip");

        try (
             FileOutputStream os = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(os);
             ) {

            for (File file : files) {
                String name = file.getName();
                ZipEntry zipEntry = new ZipEntry(name);
                zos.putNextEntry(zipEntry);
                zos.write(FileCopyUtils.copyToByteArray(file));
                zos.closeEntry();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return zipFile;
    }


    /**
     * redisson锁使用范例
     * 1.尝试获取某个锁, 等待x(秒), 若成功获取到锁则设置锁自动释放时间y(秒)并继续执行,否则直接返回
     * 2.最后一定要释放锁
     *
     * @return boolean
     * @author jinjiayu
     */
    public boolean redissonLockTest() {
        try {
            // tryLock尝试加锁,等待0秒,5秒超时直接释放锁
            if (!rLockDemo.tryLock(0,5, TimeUnit.SECONDS)) {
                return false;
            }

            // Todo 具体的业务处理代码
            System.out.println("具体的业务处理代码");
        } catch (Exception e) {
            log.error("任务执行失败!" + e.getMessage());
            return false;
        } finally {
            rLockDemo.unlock();
        }

        return true;
    }

    public static void getFilePath() throws UnsupportedEncodingException {
        //        File file1 = new File("C:\\Users\\49993\\Pictures\\搞笑\\1.jpg");
//        System.out.println(file1.exists() + file1.getName());
//
//        File file2 = new File("./src/main/resources/application.yml");
//        System.out.println(file2.exists() + file2.getName());
        System.out.println(TestService.class.getResource("").getPath());
        System.out.println(TestService.class.getResource(".").getPath());
        System.out.println(TestService.class.getResource(".").getFile());
        System.out.println(TestService.class.getClassLoader().getResource("application.yml").getPath());
        System.out.println(TestService.class.getClassLoader().getResource("application.yml").getFile());
        File file1 = new File(URLDecoder.decode(TestService.class.getClassLoader().getResource("application.yml").getFile(), "utf-8"));
        System.out.println(file1.exists() + " " + file1.getName());
//        File file1 = new File("/D:/Project/私人/space/target/classes/application.yml");
//        System.out.println(file1.exists() + " " + file1.getName());
        System.out.println(new String("测试.jpg".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8));
    }


    public static void main(String[] args) throws UnsupportedEncodingException {
        TestService testService = new TestService();
        System.out.println(testService);
        testService(testService);
        System.out.println(testService);

    }

    public static void testService(TestService testService) {
        testService = new TestService();
        System.out.println(testService);
    }

    public static void testGeneric() {
        // objects1 可以添加UserInfo类型或它的子类,可以用UserInfo或它的父类接受
        ArrayList<UserInfo> objects1 = new ArrayList<>();
        // objects2 <?> 类似于 <? extends Object>, 只能取出Object类型, 不能添加元素
        ArrayList<?> objects2 = new ArrayList<>();
        // objects2 只能取出UserInfo类型或它的父类, 不能添加元素
        ArrayList<? extends UserInfo> objects3 = new ArrayList<>();
        // objects2 只能取出Object类型, 可以添加UserInfo或它的子类
        ArrayList<? super UserInfo> objects4 = new ArrayList<>();
        //objects2 = objects1;
        //objects3 = objects1;
        //objects4 = objects1;
        //objects5 = objects1;

        //objects1.add("1");
        objects1.get(0);
        //objects2.add("1");
        objects2.get(0);
        //objects3.add("1");
        objects3.get(0);
        objects4.add(new UserInfo());
        objects4.get(0);
    }


    /**
     * redis-新增缓存
     * 参考文档: 无
     *
     * @param key
     * @param value
     * @param timeMinutes
     * @author jinjiayu
     */
    public void redisCreate(String key, String value, int timeMinutes) {
        redisTemplate.opsForValue().set(key, value, timeMinutes, TimeUnit.MINUTES);
    }

    /**
     * redis-模糊查询(如 abc 或 abc*)
     * 参考文档: 无
     *
     * @param pattern
     * @return Result
     * @author jinjiayu
     */
    public Result redisKeyValueQuery(String pattern) {
        Set<String> keys = redisTemplate.keys(pattern);
        if (keys == null) {
            return Result.successSingle("缓存不存在!");
        }
        List<Object> values = redisTemplate.opsForValue().multiGet(keys);
        if (values == null) {
            return Result.successSingle("缓存不存在!");
        }

        JSONArray jsonArray = new JSONArray();
        Object[] keysArray = keys.toArray();
        for (int i = 0; i < keys.size(); i++) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("key", keysArray[i]);
            jsonObject.put("value", values.get(i));
            jsonObject.put("expire", redisTemplate.opsForValue().getOperations().getExpire((String) keysArray[i]));
            jsonArray.add(jsonObject);
        }
        return Result.success(jsonArray, jsonArray.size());
    }

    /**
     * redis-模糊删除(如 abc 或 abc*)
     * 参考文档: 无
     *
     * @param pattern
     * @return Result
     * @author jinjiayu
     */
    public Result redisDeleteBatch(String pattern) {
        Set<String> keys = redisTemplate.keys(pattern);
        if (CollectionUtils.isEmpty(keys)) {
            return Result.fail("删除失败!");
        }
        int i = 0;
        for (String item : keys) {
            Boolean result = redisTemplate.delete(item);
            if (result != null && result) {
                i++;
            }
        }
        return Result.successSingle("删除成功数量:" + i);
    }

}

